/* 
 * This code is part of the smartlink project, see
 * 
 * 		http://code.google.com/p/smartlink/.
 * 
 * Copyright (C) 2008 Oliver Noelle
 * 
 * SmartLink is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * SmartLink is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public License
 * along with SmartLink.  If not, see <http://www.gnu.org/licenses/>.
 **/
package org.smartlink;

import java.util.List;

import org.smartlink.provider.SmartLink;
import org.smartlink.search.FuzzySearch;

import android.app.AlertDialog;
import android.app.ListActivity;
import android.content.ComponentName;
import android.content.ContentValues;
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
import android.text.TextWatcher;
import android.util.Log;
import android.view.Menu;
import android.view.SubMenu;
import android.view.View;
import android.view.Menu.Item;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ListView;

/**
 * The main view of the smartlink application, with the list of best matches.
 */
public class LinkList extends ListActivity implements TextWatcher {

	private static final int PICK_TARGET_START_INDEX = 100;

	private static final String TAG = "LinkList";

	// Menu item Ids
	public static final int FOLLOW_LINK_ID = Menu.FIRST;
	public static final int USE_TARGET_ID = Menu.FIRST + 1;
	public static final int EDIT_LINK_KEY_ID = Menu.FIRST + 2;
	public static final int CREATE_LINK_ID = Menu.FIRST + 3;
	public static final int DELETE_LINK_ID = Menu.FIRST + 4;
	public static final int FIRST_SUBMENU_ID = DELETE_LINK_ID + 1;

	// projection indices
	private static final int ID_INDEX = 0;
	private static final int KEY_INDEX = 1;
	private static final int TARGET_INDEX = 2;
	private static final int DESCRIPTION_INDEX = 3;

	private static final String[] PROJECTION = new String[] { SmartLink.Links._ID, SmartLink.Links.KEY,
			SmartLink.Links.TARGET, SmartLink.Links.DESCRIPTION };

	/** list of activities offering to make use of a target url */
	private List<ResolveInfo> useTargetActivityList;
	/** list of activites offering to pick a target url */
	private List<ResolveInfo> pickActivityList;

	/** the state in which smartlink was launched */
	private int mState;
	/** invoked from launcher */
	private static final int STATE_SMARTLINK_INVOKED = 0;
	/** invoked from client app */
	private static final int STATE_CLIENT_INVOKED = 1;

	private Cursor mCursor;
	private String mSearchTerm;
	private EditText mSearchField;
	private BestMatchAdapter mAdapter;
	private ListView mListView;
	private FuzzySearch search;

	/** set to false to hide the link management menu options */
	private boolean fullMenu = true;

	private SubMenu useTargetSubMenu;
	private SubMenu pickTargetSubMenu;

	/** the item clicked on in touch mode */
	private BestMatchView clickedItem;

	@Override
	protected void onCreate(Bundle icicle) {
		super.onCreate(icicle);

		setDefaultKeyMode(SHORTCUT_DEFAULT_KEYS);
		search = new FuzzySearch(2, 5);

		Intent intent = getIntent();
		if (intent.getData() == null) {
			// smartlink was started as application
			mState = STATE_SMARTLINK_INVOKED;
			intent.setData(SmartLink.Links.CONTENT_URI);
			mSearchTerm = "";
		} else {
			// smartlink activity started from a client app
			mState = STATE_CLIENT_INVOKED;
			mSearchTerm = intent.getExtras().getString(Client.EXTRA_SEARCH_KEY);
			intent.setData(SmartLink.Links.CONTENT_URI);
		}

		setupView();
		mAdapter = new BestMatchAdapter(this, android.R.layout.simple_list_item_1, search.getBestMatchesList());
		updateMatches();
		setListAdapter(mAdapter);
		// getListView().setOnItemSelectedListener(this);
	}

	/**
	 * setup the view itself (add stripes)
	 */
	private void setupView() {
		setContentView(R.layout.link_list);
		mListView = (ListView) findViewById(android.R.id.list);
		mListView.setFocusable(true);
		mListView.setFocusableInTouchMode(true);
		mSearchField = (EditText) findViewById(R.id.search_term);
		if (mSearchTerm == null) {
			mSearchField.setText("");
		} else {
			mSearchField.setText(mSearchTerm);
		}
		mSearchField.setSingleLine();
		mSearchField.addTextChangedListener(this);
		Button button = (Button) findViewById(R.id.new_link);
		button.setOnClickListener(new Button.OnClickListener() {
			@Override
			public void onClick(View arg0) {
				createLink();
			}
		});
	}

	@Override
	protected void onResume() {
		Log.w(TAG, "onResume()");
		super.onResume();
		updateMatches();
	}

	/** Updates the best matches list when the search term has changed. */
	public void updateMatches() {
		search.prepareSearch(mSearchTerm);
		mCursor = managedQuery(getIntent().getData(), PROJECTION, null, null);
		for (int i = 0; mCursor.next(); i++) {
			Link link = new Link(i, mCursor.getString(ID_INDEX), mCursor.getString(KEY_INDEX), mCursor
					.getString(TARGET_INDEX), mCursor.getString(DESCRIPTION_INDEX));
			search.checkNext(link.key, link);
		}
		mAdapter.notifyDataSetChanged();
	}

	@Override
	public boolean onCreateOptionsMenu(Menu menu) {
		super.onCreateOptionsMenu(menu);
		menu.add(0, FOLLOW_LINK_ID, R.string.menu_follow_link, R.drawable.follow_link);
		menu.add(0, EDIT_LINK_KEY_ID, R.string.menu_edit_link_key, R.drawable.edit_link_key);
		menu.add(0, CREATE_LINK_ID, R.string.menu_insert_link, R.drawable.add_link);
		menu.add(0, DELETE_LINK_ID, R.string.menu_delete_link, R.drawable.remove_link);
		useTargetSubMenu = menu.addSubMenu(0, USE_TARGET_ID, R.string.submenu_use, R.drawable.use);
		pickTargetSubMenu = menu.addSubMenu(0, PICK_TARGET_START_INDEX, R.string.submenu_pick_target, R.drawable.use);
		return true;
	}

	@Override
	public boolean onPrepareOptionsMenu(Menu menu) {
		super.onPrepareOptionsMenu(menu);
		final boolean isItemSelected = (getSelectedLink() != null);
		menu.setItemShown(FOLLOW_LINK_ID, isItemSelected);
		menu.setItemShown(EDIT_LINK_KEY_ID, fullMenu && isItemSelected);
		menu.setItemShown(CREATE_LINK_ID, fullMenu);
		menu.setItemShown(DELETE_LINK_ID, fullMenu && isItemSelected);
		menu.setItemShown(USE_TARGET_ID, false);
		if (isItemSelected && mState == STATE_CLIENT_INVOKED) {
			// resolve all activities that offer to return a piece of text
			// based on target url (can we move this to onCreateOptionsMenu?)
			Uri uri = Uri.parse(getSelectedLink().target);
			ComponentName component = new ComponentName(this, LinkList.class);
			Intent intent = new Intent("org.smartlink.action.USE_TARGET_ACTION", uri);
			intent.addCategory(Intent.ALTERNATIVE_CATEGORY);
			PackageManager pm = getPackageManager();
			useTargetActivityList = pm.queryIntentActivityOptions(component, null, intent, 0);
			useTargetSubMenu.clear();
			if (useTargetActivityList.size() > 0) {
				menu.setItemShown(USE_TARGET_ID, true);
				int id = FIRST_SUBMENU_ID;
				for (ResolveInfo resolveInfo : useTargetActivityList) {
					CharSequence label = resolveInfo.loadLabel(pm);
					useTargetSubMenu.add(0, id, label);
					id++;
				}
			}
		}
		menu.setItemShown(PICK_TARGET_START_INDEX, false);
		if (fullMenu && isItemSelected) {
			// resolve all activities that offer to pick a target url
			// (can we move this to onCreateOptionsMenu?)
			ComponentName component = new ComponentName(this, LinkList.class);
			Uri uri = Uri.withAppendedPath(SmartLink.Links.CONTENT_URI, getSelectedLink().id);
			Intent intent = new Intent("org.smartlink.action.PICK_TARGET_ACTION", uri);
			intent.addCategory(Intent.ALTERNATIVE_CATEGORY);
			PackageManager pm = getPackageManager();
			pickActivityList = pm.queryIntentActivityOptions(component, null, intent, 0);
			pickTargetSubMenu.clear();
			if (pickActivityList.size() > 0) {
				int index = PICK_TARGET_START_INDEX + 1;
				menu.setItemShown(PICK_TARGET_START_INDEX, true);
				for (ResolveInfo resolveInfo : pickActivityList) {
					Item item = pickTargetSubMenu.add(0, index, resolveInfo.loadLabel(pm));
					item.setIcon(resolveInfo.loadIcon(pm));
					index++;
				}
			}
		}
		return true;
	}

	@Override
	public boolean onOptionsItemSelected(Menu.Item item) {
		if (item.getId() >= PICK_TARGET_START_INDEX) {
			if (item.getId() == PICK_TARGET_START_INDEX) {
				// this is just the submenu being selected => ignore
				return true;
			}
			// all resolved pick target activities
			ResolveInfo resolveInfo = pickActivityList.get(item.getId() - 101);
			String packageName = resolveInfo.activityInfo.applicationInfo.packageName;
			String className = resolveInfo.activityInfo.name;
			ComponentName component = new ComponentName(packageName, className);
			Intent intent = new Intent("org.smartlink.action.PICK_TARGET_ACTION");
			intent.setComponent(component);
			startSubActivity(intent, SmartLink.PICK_TARGET_REQUEST_CODE);
			return true;
		}
		switch (item.getId()) {
		case FOLLOW_LINK_ID:
			followTarget();
			return true;
		case EDIT_LINK_KEY_ID:
			editLinkKey();
			return true;
		case CREATE_LINK_ID:
			createLink();
			return true;
		case DELETE_LINK_ID:
			deleteLink();
			return true;
		case USE_TARGET_ID:
			// this is just the submenu being selected => ignore
			return true;
		default:
			ActivityInfo activityInfo = useTargetActivityList.get(item.getId() - FIRST_SUBMENU_ID).activityInfo;
			String packageName = activityInfo.applicationInfo.packageName;
			String className = activityInfo.name;
			ComponentName component = new ComponentName(packageName, className);
			Uri targetContentUri = Uri.parse(getSelectedLink().target);
			Intent intent = new Intent(SmartLink.SMARTLINK_USE_TARGET_ACTION, targetContentUri);
			intent.setComponent(component);
			startSubActivity(intent, SmartLink.USE_TARGET_REQUEST_CODE);
			return true;
		}
	}

	/** user clicked on item => offer reduced menu */
	@Override
	protected void onListItemClick(ListView l, View v, int position, long id) {
		clickedItem = (BestMatchView) v;
		if (mState != STATE_CLIENT_INVOKED) {
			followTarget();
		} else {
			fullMenu = false;
			// TODO: if no use handler registered, jump directly to View?
			openOptionsMenu();
			fullMenu = true;
		}
	}

	// not link-specific actions
	// **************************

	/** creates a new association and starts to edit it */
	private final void createLink() {
		Intent intent = new Intent(Intent.INSERT_ACTION, getIntent().getData());
		intent.putExtra(SmartLink.Links.EXTRA_CREATE_LINK, mSearchTerm);
		startSubActivity(intent, SmartLink.CREATE_LINK_REQUEST_CODE);
	}

	// link related actions
	// ********************

	/** deletes the association */
	private final void deleteLink() {
		mCursor.moveTo(getSelectedLink().cursorPos);
		mCursor.deleteRow();
		updateMatches();
	}

	/** edits a link's key */
	private void editLinkKey() {
		Uri uri = Uri.withAppendedPath(SmartLink.Links.CONTENT_URI, getSelectedLink().id);
		Intent intent = new Intent(KeyEditor.EDIT_KEY_ACTION, uri);
		intent.putExtra(SmartLink.Links.EXTRA_CREATE_LINK, getSelectedLink().key);
		startSubActivity(intent, SmartLink.EDIT_LINK_KEY_REQUEST_CODE);
	}

	/** follows the link target */
	private final void followTarget() {
		Link link = getSelectedLink();
		if (link.target == null || link.target.equals("")) {
			AlertDialog.show(this, "Invalid Link Target", 0, "There is no target specified for this link!", "OK", true);
			Log.w(TAG, "invalid target for key: " + link.key + ", target: " + link.target);
			return;
		}
		Uri targetContentUri = Uri.parse(getSelectedLink().target);
		Intent intent = new Intent(Intent.VIEW_ACTION, targetContentUri);
		startSubActivity(intent, SmartLink.FOLLOW_TARGET_REQUEST_CODE);
	}

	// end of link actions
	// ********************

	protected void onActivityResult(int requestCode, int resultCode, String data, Bundle extras) {
		Log.w(TAG, "onActivityResult(), data: " + data);
		switch (requestCode) {
		case SmartLink.EDIT_LINK_KEY_REQUEST_CODE:
			if (resultCode == RESULT_CANCELED) {
				return;
			}
			mCursor.requery();
			mCursor.moveTo(getSelectedLink().cursorPos);
			// edited key string is returned from activity in result data
			mCursor.updateString(KEY_INDEX, data);
			managedCommitUpdates(mCursor);
			return;
		case SmartLink.USE_TARGET_REQUEST_CODE:
			// activity from server app has selected text from item to use
			// => return it to client app
			setResult(RESULT_OK, data);
			finish();
			return;
		case SmartLink.FOLLOW_TARGET_REQUEST_CODE:
			// TODO: we cannot distinguish "failure to follow" from "cancel
			// editing" (= going
			// back from it)
			// if (resultCode == RESULT_CANCELED) {
			// new AlertDialog.Builder(this).setTitle(
			// getResources().getString(R.string.err_msg_invalid_target) + "\n"
			// + getSelectedLink().target)
			// .setNegativeButton(R.string.button_ok, new
			// DialogInterface.OnClickListener() {
			// public void onClick(DialogInterface dialog, int whichButton) {
			// /* User clicked No so do some stuff */
			// }
			// }).show();
			// return;
			// }
			// activity to edit target has returned
			// finish SmartLink activity depending on how we were launched
			if (mState == STATE_CLIENT_INVOKED) {
				finish();
			}
			return;
		case SmartLink.PICK_TARGET_REQUEST_CODE:
			if (resultCode == RESULT_CANCELED) {
				return;
			}
			String desc = null;
			if (extras != null) {
				desc = extras.getString(Server.EXTRA_ITEM_DESCRIPTION);
			}
			if (desc == null) {
				// fallback if server does not provide a description: use uri
				desc = data;
			}
			// update picked target url and descriptive text in db
			mCursor.requery();
			mCursor.moveTo(getSelectedLink().cursorPos);
			mCursor.updateString(TARGET_INDEX, data);
			mCursor.updateString(DESCRIPTION_INDEX, desc);
			managedCommitUpdates(mCursor);
			return;
		case SmartLink.PICK_TARGET_FOR_NEW_LINK_REQUEST_CODE:
			// special case for a newly created link
			desc = null;
			Uri uri = null;
			if (extras != null) {
				desc = extras.getString(Server.EXTRA_ITEM_DESCRIPTION);
				uri = Uri.parse(extras.getString(Server.EXTRA_URI));
			}
			if (desc == null) {
				// fallback if server does not provide a description: use uri
				desc = data;
			}
			if (uri == null) {
				Log.w(TAG, "uri is null!");
				return;
			}
			if (resultCode == RESULT_CANCELED) {
				int rowsDeleted = getContentResolver().delete(uri, null, null);
				if (rowsDeleted != 1) {
					Log.w(TAG, "problems canceling new link");
				}
				return;
			}
			// update picked target url and descriptive text in db
			ContentValues newValues = new ContentValues();
			newValues.put(SmartLink.Links.DESCRIPTION, desc);
			newValues.put(SmartLink.Links.TARGET, data);
			getContentResolver().update(uri, newValues, null, null);
			return;
		case SmartLink.CREATE_LINK_REQUEST_CODE:
			if (resultCode == RESULT_CANCELED) {
				Log.w(TAG, "no link created");
				return;
			}
			// insert row with the key (but no target yet) into db
			ContentValues initialValues = new ContentValues();
			initialValues.put(SmartLink.Links.KEY, data);
			Uri insertedUri = getContentResolver().insert(getIntent().getData(), initialValues);

			// call pick target activity
			Intent intent = new Intent(SmartLink.SMARTLINK_PICK_TARGET_ACTION2, insertedUri);
			startSubActivity(intent, SmartLink.PICK_TARGET_FOR_NEW_LINK_REQUEST_CODE);
			return;
		default:
			Log.w(TAG, "unexpected requestCode: " + requestCode);
			return;
		}
	}

	public void beforeTextChanged(CharSequence s, int start, int count, int after) {
	}

	public void onTextChanged(CharSequence s, int start, int before, int count) {
		mSearchTerm = mSearchField.getText().toString();
		updateMatches();
	}

	/** @return the currently selected link, or null if no selection. */
	private Link getSelectedLink() {
		// we have to distinguish between touch mode and normal mode
		if (mListView.inTouchMode()) {
			return clickedItem.getLink();
		} else {
			if (mListView.hasFocus()) {
				return ((BestMatchView) mListView.getSelectedView()).getLink();
			} else {
				return null;
			}
		}
	}

}
